---
title: 基于 GitHub Actions + OSS 的自托管 APT 仓库全面指南
description: 手把手搭建支持双端部署的 Debian 软件仓库，实现自动化构建、安全签名与全球加速，再也不用每次都去 Github Release 下载软件包了。
date: "2025-04-29"
lang: zh
tags:
  - Debian
  - 环境配置
  - 持续集成
  - 云原生
category: 技术
cover: https://cdn.qladgk.com/images/20250508161000728.png
---

import Image from "@/components/mdx/Image";

<Image
  src="https://cdn.qladgk.com/images/20250508161000728.png"
  alt="cover"
  width={999}
  height={527}
  isArticleImage={true}
/>

## 效果预览

只需要在软件列表文件中添加你想要入库的 github 链接地址即可，比如：

<Image
  src="https://cdn.qladgk.com/images/20250429151950431.png"
  alt="APT 仓库软件列表"
  width={999}
  height={527}
  isArticleImage={true}
/>

当软件列表文件更新后会生成带有索引目录的 APT 仓库

<Image
  src="https://cdn.qladgk.com/images/20250429141441957.png"
  alt="索引目录的 APT 仓库"
  width={999}
  height={527}
  isArticleImage={true}
/>

核心优势

- 跨平台分发：GitHub Pages（国际） + 阿里云 OSS（国内）双通道
- 军工级安全：GPG 签名 + 密钥分层管理
- 极速构建：单次构建耗时 < 2 分钟（实测数据）
- 零成本起步：GitHub Actions 免费配额

如果你是专家，你可以直接 Fork 我的仓库改改就能用：https://github.com/qlAD/aptRepo

## 前置知识

### APT 仓库如何工作

首先要明白 APT 仓库其实就是一个规范整理的文件夹，里面包含了软件包和索引文件。

具体的目录结构如下：

```json
.
├── conf
│   └── distributions
├── db
│   ├── checksums.db
│   ├── contents.cache.db
│   ├── packages.db
│   ├── references.db
│   ├── release.caches.db
│   └── version
├── dists
│   └── sid
│       ├── InRelease
│       ├── main
│       │   └── binary-amd64
│       │       ├── Packages
│       │       ├── Packages.gz
│       │       └── Release
│       ├── Release
│       └── Release.gpg
└── pool
    └── main
        └── c
            └── clash-verge
                └── clash-verge_2.2.3_amd64.deb
```

我们需要做的就是整理好这些文件放到一个可以被访问的地方，并且让 APT 客户端知道这个地方。

整理文件很简单，有一个自动化的工具叫做 `reprepro`，它会自动帮你生成这些文件。

然后把这些文件放到一个可以被访问的地方，比如 Web 服务器、GitHub Pages 或者阿里云 OSS。

最后在 `/etc/apt/sources.list.d` 中添加一个 `.sources` 文件，告诉 APT 客户端这个地方在哪里。

```yml
# /etc/apt/sources.list.d/qladgk.sources
Types: deb
URIs: https://apt-repo.qladgk.com/debian
Suites: sid
Components: main
Signed-By: /etc/apt/keyrings/qladgk.asc
```

然后当你运行 `apt update` 的时候，APT 客户端就会去这个地方下载索引文件，然后根据索引文件去下载软件包。

### GPG 签名

GPG 密钥就好像互联网上的另一个自己，私钥就像是你的身份证，公钥就像是你的名片。

公钥可以随意发给别人，私钥必须要保管好。

APT 仓库的软件包都需要进行 GPG 签名，这样才能保证文件的完整性和安全性，或者直接对整个仓库进行签名。

如果你不进行签名，APT 客户端会报错，提示你这个仓库不可信。

如果你泄露了私钥，别人可以通过你的私钥对软件包进行签名，这样就可以伪造软件包，APT 客户端会认为这个软件包是可信的。

## 部署搭建步骤

### 1. 生成 GPG 密钥对

首先需要安装 GPG 工具，如果你使用的是 Debian 或 Ubuntu，可以直接使用以下命令安装：

```bash
sudo apt install gnupg
```

接下来生成 GPG 密钥对，使用以下命令：

```bash
gpg --full-generate-key
```

在生成过程中，你需要选择密钥类型、密钥大小、有效期等信息。

其中密钥类型包括如下选项

<Image
  src="https://cdn.qladgk.com/images/20250429144738438.png"
  alt="GPG 密钥类型"
  width={500}
  height={527}
  isArticleImage={true}
/>

其中默认的 ECC（椭圆曲线密码学）提供了更高的安全性与更短的密钥长度，但它在某些旧系统中可能不被支持

所以这里就选择第一项 RSA 和 RSAi 就行

然后密钥大小建议选择 4096 位，这样可以提供更高的安全性

有效期可以根据需要设置，建议设置为 1 年以上，因为 Debian 的 apt 会警告一年内即将过期的密钥，这里我选的是永不过期

当所有信息填完之后会打开密码输入框来加密你的密钥，有密码的好处是即使泄露了私钥，别人也不能通过一个带密码的私钥来签名

全部截图如下：

<Image
  src="https://cdn.qladgk.com/images/20250429141441886.png"
  alt="生成带密码的 GPG 密钥"
  width={999}
  height={527}
  isArticleImage={true}
/>

到此密钥对生成成功，接下来需要把密钥对导出，然后私钥给到 Github Action 让它帮我们自动签名，把公钥暴露在网络上供人使用

### 2. 导出密钥对

首先我们可以查看刚才创建的密钥对

```bash
gpg --list-secret-keys
```

确认密钥存在后，使用 ASCII 的形式导出

> 因为我们要把私钥复制到 Github Action 中的环境变量里，所以要用 ASCII 这种方便复制的形式

```bash
gpg -a -o public-key.asc --export 你的密钥中的邮箱 #导出公钥
gpg -a -o private-key.asc --export-secret-keys 你的密钥中的邮箱 #导出私钥
```

导出密钥的时候需要输入你刚才设置的密码

> -a 为 –armor 的简写，表示密钥以 ASCII 的形式输出，不加则默认以二进制的形式 gpg 输出

> -o 为 –output 的简写，指定写入的文件

全部截图如下：

<Image
  src="https://cdn.qladgk.com/images/20250429141441904.png"
  alt="导出 GPG 密钥"
  width={999}
  height={527}
  isArticleImage={true}
/>

密钥创建并导出之后就可以开始设置 Github 仓库

### 3. 创建 GitHub 仓库

由于我们要使用 GitHub Actions 来自动化构建 APT 仓库，并且会用到 Github Pages 来搭建仓库的访问地址，所以首先需要一个 GitHub 仓库来存放我们的代码和配置。

首先在 GitHub 上创建一个新的仓库，比如 `apt-repo`。

<Image
  src="https://cdn.qladgk.com/images/20250429143649758.png"
  alt="创建 GitHub 仓库"
  width={999}
  height={527}
  isArticleImage={true}
/>

然后来到仓库的设置页面，允许 Actions 的一些权限

<Image
  src="https://cdn.qladgk.com/images/20250429144039906.png"
  alt="允许 Actions 权限"
  width={999}
  height={527}
  isArticleImage={true}
/>

接着设置一些环境变量，如下

<Image
  src="https://cdn.qladgk.com/images/20250429150821389.png"
  alt="Actions 的环境变量"
  width={999}
  height={527}
  isArticleImage={true}
/>

其中前三个与阿里云的 OSS 相关，如果你没有阿里云的 OSS 可以不设置

后两个是 GPG 的私钥和密码，复制私钥和输入私钥的密码就行

### 4. 配置 GitHub Actions

在仓库根目录下创建一个 `.github/workflows` 目录，然后在里面创建一个 `update-repo.yml` 文件。

内容如下：

```yml
name: Update APT Repository

on:
  workflow_dispatch:
  push:
    paths:
      - "packages.list"

jobs:
  build-repo:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Environment
        run: |
          sudo apt-get update
          sudo apt-get install -y reprepro gnupg wget

      - name: Import GPG key
        uses: crazy-max/ghaction-import-gpg@v6
        with:
          gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
          passphrase: ${{ secrets.GPG_PASSPHRASE }}

      - name: Fetch Packages
        run: |
          mkdir -p downloaded_packages
          ./scripts/fetch-packages.sh

      - name: Build Repo
        run: |
          ./scripts/setup-reprepro.sh

      - name: Upload APT Repo to Aliyun OSS
        uses: fangbinwei/aliyun-oss-website-action@v1
        with:
          accessKeyId: ${{ secrets.ACCESS_KEY_ID }}
          accessKeySecret: ${{ secrets.ACCESS_KEY_SECRET }}
          bucket: ${{ secrets.ACCESS_BUCKET_NAME }}
          endpoint: oss-cn-shanghai.aliyuncs.com
          folder: ./gh-pages

      - name: Generate Directory Listings
        uses: jayanta525/github-pages-directory-listing@v4.0.0
        with:
          FOLDER: ./gh-pages

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3.0.1
        with:
          path: "./gh-pages"

  deploy-index:
    needs: build-repo
    permissions:
      pages: write
      id-token: write
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4.0.0
```

支持手动触发和当 `packages.list` 有更新后自动触发，当然你也可以设置每周触发一次，但是频率不要太高

如果你不需要阿里云的 OSS 部署就将这部分删掉

```yml
- name: Upload APT Repo to Aliyun OSS
  uses: fangbinwei/aliyun-oss-website-action@v1
  with:
    accessKeyId: ${{ secrets.ACCESS_KEY_ID }}
    accessKeySecret: ${{ secrets.ACCESS_KEY_SECRET }}
    bucket: ${{ secrets.ACCESS_BUCKET_NAME }}
    endpoint: oss-cn-shanghai.aliyuncs.com
    folder: ./gh-pages
```

其中还用到了两个 action 的模板，如果有想了解的自己去看

### 5. 编写脚本

`./scripts/fetch-packages.sh` 用于下载软件列表 `packages.list` 中特点架构的软件包

```bash
#!/bin/bash

REPO_LIST="packages.list"
TMP_DIR="downloaded_packages"

while IFS= read -r repo_url; do
  repo_owner=$(echo "$repo_url" | cut -d'/' -f4)
  repo_name=$(echo "$repo_url" | cut -d'/' -f5)

  # 仅下载 amd64 架构的包
  assets=$(curl -s "https://api.github.com/repos/${repo_owner}/${repo_name}/releases/latest" |
           jq -r '.assets[] | select(.name | test(".*_amd64\\.deb$")) | .browser_download_url')

  for asset_url in $assets; do
    wget -P "$TMP_DIR" "$asset_url"
  done
done < "$REPO_LIST"
```

`packages.list` 文件中每一行对应一个 Github 仓库

`./scripts/setup-reprepro.sh` 用来创建 APT 仓库所需要的目录结构

```bash
#!/bin/bash

REPO_DIR="gh-pages/debian"
CONF_DIR="repo-config"

# 清理旧数据
rm -rf "$REPO_DIR" "$CONF_DIR"

# 初始化配置目录
mkdir -p "$REPO_DIR"
mkdir -p "$CONF_DIR"/conf

Codename="sid"
Suite="$Codename"

# 生成核心配置文件
cat > "$CONF_DIR"/conf/distributions <<EOF
Origin: apt-repo
Label: apt-repo
Description: Self APT Repository
Codename: $Codename
Suite: $Suite
Architectures: amd64
Components: main
SignWith: yes
EOF

# 导入软件包
for deb in downloaded_packages/*.deb; do
  # 导入到仓库
  reprepro -V -b "$CONF_DIR" -S misc includedeb "$Codename" "$deb"
done

# 生成元数据
reprepro -b "$CONF_DIR" export "$Codename"

# 合并文件到发布目录
rsync -a "$CONF_DIR"/ "$REPO_DIR/"
```

关于 distribution 中 codename/component/suite 这些术语，debian 官方是有解释

一般 codenam 与 suite 一致，都是各个版本的代号，比如 `bionic`、`xenial`、`trixie`

另外别忘了给脚本加上可执行权限

### 6. Github Pages

接下来在仓库的设置页面，开启 GitHub Pages 功能，选择 `Actions` 作为源

<Image
  src="https://cdn.qladgk.com/images/20250429144146681.png"
  alt="开启 GitHub Pages 功能"
  width={999}
  height={527}
  isArticleImage={true}
/>

最后别忘了你还要上传一个 `packages.list` 文件

最后结构应该如下所示

<Image
  src="https://cdn.qladgk.com/images/20250429153717926.png"
  alt="最后结构"
  width={450}
  height={527}
  isArticleImage={true}
/>

然后只用运行 Action 就会自动完成所有事

## 验收成果

### 查看索引网页

本身的 APT 仓库是只能被 apt line 识别，没有索引。但是我在 action 里面加了搭建索引的步骤

只需要在浏览器访问你的 pages 地址即可，因为我自定义了域名所以是 https://repos.qladgk.com/debian/

你应该会看到如下界面

<Image
  src="https://cdn.qladgk.com/images/20250429141441957.png"
  alt="索引目录的 APT 仓库"
  width={999}
  height={527}
  isArticleImage={true}
/>

其实索引并不重要，只是方便网页查看而已

### 导入这个 apt 仓库

首先要接受别人的仓库你需要下载别人仓库提供的公钥，如果你还不知道公钥是什么，就往前翻

我的公钥的直链下载链接在 https://apt-repo.qladgk.com/public-key.asc

所以我可以直接用命令行下载

```bash
sudo apt-get update
sudo apt-get install ca-certificates curl #必须安装的工具
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://apt-repo.qladgk.com/public-key.asc -o /etc/apt/keyrings/qladgk.asc
sudo chmod a+r /etc/apt/keyrings/qladgk.asc
```

这里将下载的公钥存放在 /etc/apt/keyrings 也是 Debian 官方推荐的，现在很少用 `apt-key add` 这个命令，也不推荐放在 `/etc/apt/trusted.gpg.d` 或者 `/usr/share/keyrings/`

然后创建软件源管理文件，推荐使用 DEB822 格式 /etc/apt/sources.list.d/xxxxx.sources 来管理软件源

创建 `/etc/apt/sources.list.d/qladgk.sources` 文件，内容如下：

```yml
Types: deb
URIs: https://repos.qladgk.com/debian
Suites: sid
Components: main
Signed-By: /etc/apt/keyrings/qladgk.asc
```

其中的 Suites、Components 按照你的仓库来填写

### 测试安装软件

接下来执行 `apt update` 你就能看见 apt 已经获取到了这个仓库的数据

完整截图如下：

<Image
  src="https://cdn.qladgk.com/images/20250429141441923.png"
  alt="导入仓库"
  width={999}
  height={527}
  isArticleImage={true}
/>

然后尝试安装你仓库里的软件包

<Image
  src="https://cdn.qladgk.com/images/20250429141441939.png"
  alt="apt安装软件"
  width={999}
  height={527}
  isArticleImage={true}
/>

### 验证仓库签名

由于我在脚本中选择的是对整个仓库进行签名，所以需要下载仓库的密钥进行核对

在如图的位置中下载仓库签名

<Image
  src="https://cdn.qladgk.com/images/20250429155641203.png"
  alt="仓库签名"
  width={999}
  height={527}
  isArticleImage={true}
/>

然后通过 gpg 验证

```bash
gpg --verify Release.gpg Release
```

完整截图如下：

<Image
  src="https://cdn.qladgk.com/images/20250429141441981.png"
  alt="验证仓库签名"
  width={999}
  height={527}
  isArticleImage={true}
/>

## 结尾

其实只要是个标准的 apt 仓库都能通过这种方法进行管理，比如说 Docker、Chrome、Vscode

<Image
  src="https://cdn.qladgk.com/images/20250429160150461.png"
  alt="apt软件源"
  width={999}
  height={527}
  isArticleImage={true}
/>

如果你想把旧版本的 sources.list 换成 xxxx.sources 可以通过如下命令

```bash
apt modernize-sources
```

不用担心你会丢失原有的 .list 他只是被重命名成了 .list.old

最后，如何用包管理器快速方便的获取到你想要的包呢？把 Debian 换成 Arch 就行 :)
